package org.ziegler.javabase.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class RequiredNonNullSample {

    private Ball ball;

    private List<Ball> ballBucket = new ArrayList<>();

    RequiredNonNullSample(Ball ball) {
        // 情形1.1：赋值的第一时间验证。使用时验证，不容易查询空引用异常
        this.ball = Objects.requireNonNull(ball);
    }

    void setBall(Ball ball) {
        // 情形1.2：赋值的第一时间验证。使用时验证，不容易查询空引用异常
        this.ball = Objects.requireNonNull(ball);
    }

    void addBall(Ball ball) {
        // 情形2：第一时间验证对象是否为{@code null}
        Objects.requireNonNull(ball, "ball is null");

        ballBucket.add(ball);
    }

    void showBallBucket() {
        ballBucket.forEach(Ball::printBall);
    }

    /**
     * 将2个{@link Ball}的尺寸都设置为相同值{@code size}
     * @param ball1 不能为{@code null}
     * @param ball2 不能为{@code null}
     * @param size
     * @throws NullPointerException 参数为{@code ball1 == null}或者{@code ball2 == null}
     */
    public static void setBallSameSize(Ball ball1, Ball ball2, int size) {
        /**
         * 情形3:
         * 这种调用方式如果不调用Objects.requireNonNull，是不是也可以呢？
         * 因为调用Ball对象的{@link Ball#setSize(int)} ()}方法也会抛出异常。
         * 但是如果{@code ball2 == null}那么会导致ball1的状态改变，但是调用
         * {@code ball2.setSize(size);}抛出异常
         * 没能达到这个函数的要求，同时设置2个球的size，只改变了第一个Ball的尺寸，
         * 并且在发生异常之后，没能回滚操作。但通过Objects.requireNonNull验证就可以避免。
         * 总结：前置的Objects.requireNonNull验证，防止破坏对象状态，在第一时间发现异常。
         */
        Objects.requireNonNull(ball1, "ball1 is null");
        Objects.requireNonNull(ball2, "ball2 is null");

        ball1.setSize(size);
        ball2.setSize(size);
    }

    /**
     * 上面方法的错误写法
     * @param ball1
     * @param ball2
     * @param size
     */
    @Deprecated
    public static void setBallSameSizeDeprecated(Ball ball1, Ball ball2, int size) {
        /**
         * 这种调用方式如果不调用Objects.requireNonNull，是不是也可以呢？
         * 因为调用Ball对象的{@link Ball#setSize(int)} ()}方法也会抛出异常。
         * 但是如果{@code ball2 == null}那么会导致ball1的状态改变，但是调用
         * {@code ball2.setSize(size);}抛出异常
         * 没能达到这个函数的要求，同时设置2个球的size，只改变了第一个Ball的尺寸，
         * 并且在发生异常之后，没能回滚操作。但通过Objects.requireNonNull验证就可以避免。
         */

        ball1.setSize(size);
        ball2.setSize(size);
    }

    public void printBall() {
        // 因为在引用赋值的时候已经验证了ball有效，所以调用可以不验证
        ball.printBall();
    }

    public static class Ball {

        private int size;

        public Ball(int size) {
            this.size = size;
        }

        public int getSize() {
            return size;
        }

        public void setSize(int size) {
            this.size = size;
        }

        public void printBall() {
            System.out.println("ball - size = " + getSize());
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("Ball{");
            sb.append("size=").append(size);
            sb.append('}');
            return sb.toString();
        }

    }

    public static void main(String[] args) {
        Ball defaultBall = new Ball(8);
        Ball ball1 = new Ball(5);
        Ball ball2 = new Ball(11);

        RequiredNonNullSample requiredNonNullSample = new RequiredNonNullSample(defaultBall);
        requiredNonNullSample.printBall();

        requiredNonNullSample.setBall(ball1);
        requiredNonNullSample.printBall();

        requiredNonNullSample.addBall(ball1);
        requiredNonNullSample.addBall(ball2);
        requiredNonNullSample.showBallBucket();

        setBallSameSizeTest();

        setBallSameSizeDeprecatedTest();
    }

    static void setBallSameSizeTest() {
        Ball ballA = new Ball(5);
        Ball ballB = null;

        try {
            setBallSameSize(ballA, ballB, 10);
        } catch (RuntimeException e) {
            System.out.println("e = " + e);
        }

        ballA.printBall();
    }

    static void setBallSameSizeDeprecatedTest() {
        Ball ballA = new Ball(5);
        Ball ballB = null;

        try {
            setBallSameSizeDeprecated(ballA, ballB, 10);
        } catch (RuntimeException e) {
            System.out.println("e = " + e);
        }

        ballA.printBall();
    }

}
